<?php
/**
 * @author spenceryg
 * @date 2022/12/14
 * @time 3:09 下午
 * @focus love & life
 */


namespace app\auto\lib;

use app\auto\model\App;
use app\auto\model\Controller;
use app\auto\model\Method;
use app\auto\model\Table;
use gossi\formatter\Formatter;
use Laminas\Code\Generator\ClassGenerator;
use Laminas\Code\Generator\DocBlock\Tag\GenericTag;
use Laminas\Code\Generator\DocBlockGenerator;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Reflection\ClassReflection;
use PhpParser\ParserFactory;
use think\facade\Db;
use think\helper\Str;

class ControllerGenerator
{

    public static function afterGenerate()
    {
        self::beforeGenerate();
    }

    public static function beforeGenerate()
    {
        $apps = App::select();
        foreach ($apps as $app) {
            $controllers = $app->controller;
            $app_path = "../app/{$app['name']}/";
            $controller_path = $app_path . 'controller/';
            foreach ($controllers as $controller) {
                $file_path = $controller_path . $controller['name'] . '.php';
                if(is_file($file_path)) {
                    self::beforeGenerateDeal($controller);
                }
            }
        }
    }

    public static function beforeGenerateDeal($controller)
    {
        $app = $controller->app;
        $controller_path = "../app/{$app['name']}/controller/";
        $namespace = self::pathToNamespace($controller_path) .'\\';
        $class_name = $namespace .  $controller['name'] ;
        try{
            $generator = ClassGenerator::fromReflection(
                new ClassReflection($class_name)
            );
            foreach ($controller->method as $method) {
                if($generator->hasMethod($method['name'])){
                    $method_generator = $generator->getMethod($method['name']);
                    $method->code = $method_generator->getBody();
                    $docBlock = $method_generator->getDocBlock();
                    $tags = $docBlock->getTags();
                    foreach ($tags as $tag) {
                        if($tag->getName() == 'selfDefineCode'){
                            $method->is_user_define = 1;
                        }
                        if($tag->getName() == 'selfDefineView'){
                            $method->is_user_view = 1;
                        }
                    }

                    $view_file_path = ViewGenerator::getViewPath($method);
                    if(is_file($view_file_path)) {
                        $method->view_code = file_get_contents($view_file_path);
                    }

                    $method->save();

                }
            }
        }catch (\Exception $e) {
           return false;
        }



    }

    /**
     * generate
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public static function generate()
    {
        $apps = App::select();
        foreach ($apps as $app) {
            $controllers = $app->controller;
            $app_path = "../app/{$app['name']}/";
            $controller_path = $app_path . 'controller/';
            foreach ($controllers as $controller) {
                 $file_path = $controller_path . $controller['name'] . '.php';
                 $file_content = self::generateFileContent($controller);
                 file_put_contents($file_path, self::codePretty($file_content));
            }
        }
    }


    /**
     * 代码美化
     *
     * codePretty
     * @param $code
     * @return string
     */
    public static function codePretty($code): string
    {
        $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
        $ast = $parser->parse($code);
        $prettyPrinter = new \PhpParser\PrettyPrinter\Standard();
        return $prettyPrinter->prettyPrintFile($ast);
    }


    /**
     * generateFileContent
     * @param $controller
     * @return string
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public static function generateFileContent( $controller): string
    {
        $app = $controller->app;
        $table_name = env('DATABASE.PREFIX'). Str::snake($controller['name']);
        $table = Table::where('name', $table_name)->find();
        $controller_path = "../app/{$app['name']}/controller/";
        $namespace = self::pathToNamespace($controller_path);
        $tags = new GenericTag('Apidoc\Title("'.$table['comment'].'")', '');
        $tags1 = new GenericTag('Apidoc\Group("api")', '');
        $shotDesc =  $table['comment'];
        $longDesc = $app['name'] .'-'. $controller['name'];
        $docBlock = new DocBlockGenerator($shotDesc,$longDesc,[$tags,$tags1]);
        $methods = [];
        foreach ($controller->method as $method) {

            // 这里是用来生成view的
            \app\auto\lib\MethodGenerator::generate($method);

            $methodBody = \app\auto\lib\MethodGenerator::bodyGenerate($method);
            // 用户自定义的话 不需要保存生成代码
            if($method['is_user_define']) {
                $methodBody = $method['code'];
            }else {
                // 不用模型方法的原因是 这里不能删除删除view（onAfterUpdate）
                Db::table('auto_method')->save([
                    'id' => $method['id'],
                    'code' => self::codePretty($methodBody)
                ]);
            }
            // todo 其实用户自己生成的 docBlock也是有问题的 后面处理吧
            $method_tag_arr = [];
            if($method['is_user_define']) {
                $method_tag_arr[] = new GenericTag('selfDefineCode', '');
            }
            if($method['is_user_view']) {
                $method_tag_arr[] = new GenericTag('selfDefineView', '');
            }
            $methodDocBlock = \app\auto\lib\MethodGenerator::commentGenerator($method, $longDesc, $method_tag_arr);
            if($app['name'] == 'api'){
                $method_tags = [];
                $method_tags[] =  "Apidoc\Title(\"{$method['comment']}\")";
                $method_tags[] =  "Apidoc\Url(\"/{$app['name']}/{$controller['name']}/{$method['name']}\")";
                $method_tags[] =  "Apidoc\Method(\"{$method['request']}\")";
                foreach ($method->methodField as $field) {
                    if($field['trend'] == 'in') {
                        if($method['request'] == 'post') {
                            $method_tags[] = "Param(\"{$field->ifield['name']}\",type=\"{$field->ifield['type']}\",require=true,desc=\"{$field->ifield['comment']}\")";
                        }else{
                            $method_tags[] = "Query(\"{$field->ifield['name']}\",type=\"{$field->ifield['type']}\",require=true,desc=\"{$field->ifield['comment']}\")";
                        }
                    }
                }
                foreach ($method_tags as $method_tag){
                    $method_tag_arr[] = new GenericTag($method_tag, '');
                }
                $methodDocBlock = new DocBlockGenerator($method['comment'],$longDesc.'-'.$method['name'],$method_tag_arr);
            }




            $methods[] = new MethodGenerator($method['name'], [], MethodGenerator::FLAG_PUBLIC, $methodBody, $methodDocBlock);
        }
        $generator = new ClassGenerator(
            $controller['name'],
            $namespace,
            null,
            'app\\BaseController',
            [],
            [],
            $methods,
            $docBlock
        );
        $generator->addUse('hg\apidoc\annotation','Apidoc');
        $generator->addUse('hg\apidoc\annotation\Param');
        $generator->addUse('hg\apidoc\annotation\Query');
        $generator->addUse('hg\apidoc\annotation\Returned');
        $generator->addUse('hg\apidoc\annotation\Header');
        $generator->addUse('app\BaseController');
        $generator->addUse('think\facade\View');
        $generator->addUse('think\facade\Db');
        $generator->addUse('think\response\Json');
        $code = $generator->generate();
        return "<?php\n" . $code;

    }


    /**
     * pathToNamespace
     * @param string $path
     * @return string|string[]
     */
    public static function pathToNamespace(string $path)
    {
        $path = str_replace('..', '', $path);
        $path = trim($path, '/');
        return str_replace('/', "\\", $path);
    }


    /**
     * delete
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public static function delete()
    {
        $apps = App::select();
        foreach ($apps as $app) {
            $controllers = $app->controller;
            $app_path = "../app/{$app['name']}/";
            $controller_path = $app_path . 'controller/';
            foreach ($controllers as $controller) {
                $file_path = $controller_path . $controller['name'] . '.php';
                @unlink($file_path);
            }
        }
    }


    /**
     * deleteFromController
     * @param $controller
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public static function deleteFromController($controller)
    {
        $app = App::find($controller['app_id']);
        $app_path = "../app/{$app['name']}/";
        $controller_path = $app_path . 'controller/';
        $file_path = $controller_path . $controller['name'] . '.php';
        @unlink($file_path);
    }


}
